<?php
namespace Core\MCache;

use Core\MCache;

/**
 * MCache 的 APC 驱动。
 * 
 * - 注意：
 * - 1、MCache\APC 不完全兼容 MCache\Redis；
 * - 2、APC 系列函数对数据类型是敏感的，但此类对数据类型是不敏感的；
 * - 3、apc_add()、apc_store()、apc_delete()、apc_exists() 在对同一个键值进行多并发操作时表现极不可靠，应极力避免在此类情形中操作敏感数据；
 * 
 * @author SUSISOY
 */
final class APC {
	/**
	 * 最近一个操作中出现的异常。
	 * @var int
	 */
	public $error = MCache::ERROR_NONE;
	/**
	 * 当前 MCache 驱动名称。
	 * @var string
	 */
	public $driver = MCache::DRIVER_APC;
	
	/**
	 * 所有键名自动前缀的内容。
	 * @var string
	 */
	private $prefix = '';

	public function __construct($parameter) {
		isset($parameter['prefix']) && $this->prefix = $parameter['prefix'];
	}

	/**
	 * 新建一个不存在的键值及其生存时间，实际动作为“SET if Not Exists”。
	 * - $ttl 为键值的生存秒数；
	 * - 成功，返回 TRUE；
	 * - 已存在或失败，返回 FALSE；
	 * 
	 * @param string $key
	 * @param mixed $value
	 * @param int $ttl
	 * @return boolean
	 */
	public function add($key, $value, $ttl) {
		if ($ttl <= 0) {
			trigger_error('Invalid TTL', E_USER_WARNING);
		} elseif (is_scalar($key)) {
			// 如果符合整数特征，则转为整数类型（前缀“+”字符除外，因为它可能就是一个字符串），之后可执行 inc() 和 dec() 操作。
			is_scalar($value) && !is_int($value) && $value = preg_match('/^\-?\d+$/', $value) ? intval($value) : (string) $value;
			
			return apc_add($this->prefix . $key, $value, $ttl);
		} else {
			trigger_error(__CLASS__ . '::add() expects parameter 1 to be scalar', E_USER_WARNING);
		}
		
		return FALSE;
	}

	/**
	 * 新建一个不存在的且永不过期的键值，实际动作为“SET if Not Exists”。
	 * - 成功，返回 TRUE；
	 * - 已存在或失败，返回 FALSE；
	 * 
	 * @param string $key
	 * @param mixed $value
	 * @return boolean
	 */
	public function addNt($key, $value) {
		if (is_scalar($key)) {
			is_scalar($value) && !is_int($value) && $value = preg_match('/^\-?\d+$/', $value) ? intval($value) : (string) $value;
			
			return apc_add($this->prefix . $key, $value);
		} else {
			trigger_error(__CLASS__ . '::addNt() expects parameter 1 to be scalar', E_USER_WARNING);
		}
		
		return FALSE;
	}

	/**
	 * 设置一个键值及其生存时间，若键值不存在则创建之。
	 * - $ttl 为键值的生存秒数；
	 * - 成功，返回 TRUE；
	 * - 失败，返回 FALSE；
	 * 
	 * @param string $key
	 * @param mixed $value
	 * @param int $ttl
	 * @return boolean
	 */
	public function set($key, $value, $ttl) {
		if ($ttl <= 0) {
			trigger_error('Invalid TTL', E_USER_WARNING);
		} elseif (is_scalar($key)) {
			// 如果符合整数特征，则转为整数类型（前缀“+”字符除外，因为它可能就是一个字符串），之后可执行 inc() 和 dec() 操作。
			is_scalar($value) && !is_int($value) && $value = preg_match('/^\-?\d+$/', $value) ? intval($value) : (string) $value;
			
			return apc_store($this->prefix . $key, $value, $ttl);
		} else {
			trigger_error(__CLASS__ . '::set() expects parameter 1 to be scalar', E_USER_WARNING);
		}
		
		return FALSE;
	}

	/**
	 * 设置一个永不过期的键值，若键值不存在则创建之。
	 * - 成功，返回 TRUE；
	 * - 失败，返回 FALSE；
	 *
	 * @param string $key
	 * @param mixed $value
	 * @return boolean
	 */
	public function setNt($key, $value) {
		if (is_scalar($key)) {
			is_scalar($value) && !is_int($value) && $value = preg_match('/^\-?\d+$/', $value) ? intval($value) : (string) $value;
			
			return apc_store($this->prefix . $key, $value);
		} else {
			trigger_error(__CLASS__ . '::setNt() expects parameter 1 to be scalar', E_USER_WARNING);
		}
		
		return FALSE;
	}

	/**
	 * 设置多个键值及其生存时间，若键值不存在则创建之。
	 * - $ttl 为键值的生存秒数；
	 * - 返回一个数组，即设置成功的键列表；
	 * 
	 * @param array $data
	 * @param int $ttl
	 * @return array
	 */
	public function mSet($data, $ttl) {
		$result = [];
		
		if ($ttl <= 0) {
			trigger_error('Invalid TTL', E_USER_WARNING);
		} elseif (is_scalar($data)) {
			trigger_error(__CLASS__ . '::mSet() expects parameter 1 to be array', E_USER_WARNING);
		} else {
			foreach ($data as $k => &$v) {
				$this->set($k, $v, $ttl) && $result[] = $k;
			}
		}
		
		return $result;
	}

	/**
	 * 设置多个永不过期的键值，若键值不存在则创建之。
	 * - 返回一个数组，即设置成功的键列表；
	 *
	 * @param array $data
	 * @return array
	 */
	public function mSetNt($data) {
		$result = [];
		
		if (is_scalar($data)) {
			trigger_error(__CLASS__ . '::mSetNt() expects parameter 1 to be array', E_USER_WARNING);
		} else {
			foreach ($data as $k => &$v) {
				$this->setNt($k, $v) && $result[] = $k;
			}
		}
		
		return $result;
	}

	/**
	 * 条件更新一个键值。
	 * - 成功，返回 TRUE；
	 * - 失败或键值不存在，返回 FALSE；
	 * - 提示：apc_cas() 仅支持对整型数据进行操作，但 $old 和 $new 两参数值允许是整数字符串；
	 * 
	 * @param string $key
	 * @param int $old
	 * @param int $new
	 * @return boolean
	 */
	public function cas($key, $old, $new) {
		return apc_cas($this->prefix . $key, $old, $new) === TRUE; // apc_cas() 对非数字类型的数据进行操作时将抛出错误，并返回 NULL。
	}

	/**
	 * 获得指定键值。
	 * - 键值不存在，返回 NULL；
	 * 
	 * @param string $key
	 * @return mixed|NULL
	 */
	public function get($key) {
		if (is_scalar($key)) {
			$x = apc_fetch($this->prefix . $key, $x);
			if ($x !== FALSE) {
				return $x;
			}
		} else {
			trigger_error(__CLASS__ . '::get() expects parameter 1 to be scalar', E_USER_WARNING);
		}
	}

	/**
	 * 获得多个指定键值。
	 * - 键值不存在，返回数组中将不包含该键；
	 * 
	 * @param array $keys
	 * @return array
	 */
	public function mGet($keys) {
		$result = [];
		
		if (is_array($keys)) {
			$l = strlen($this->prefix);
			
			foreach ($keys as &$item) {
				$item = $this->prefix . $item;
			}
			foreach (apc_fetch($keys) as $k => $v) {
				$result[substr($k, $l)] = $v;
			}
		} else {
			trigger_error(__CLASS__ . '::mGet() expects parameter 1 to be array', E_USER_WARNING);
		}
		
		return $result;
	}

	/**
	 * 获得指定键值的过期时间。
	 * - 失败或已过期或不存在，返回负整数；
	 * - 成功，返回剩余秒数；
	 * 
	 * @param string $key
	 * @return int
	 */
	public function ttl($key) {
		$key = $this->prefix . $key;
		
		$x = apc_cache_info('user')['cache_list'];
		foreach ($x as $item) {
			if ($item['info'] == $key) {
				return $item['creation_time'] + $item['ttl'] - time();
			}
		}
		
		return -1;
	}

	/**
	 * 原子递增指定键值。
	 * - $step 为整数，浮点数将被取整（非四舍五入）；
	 * - 成功，返回递增后的值；
	 * - 键值不存在或失败，返回 FALSE；
	 * 
	 * @param string $key
	 * @param int $step
	 * @return int|FALSE
	 */
	public function inc($key, $step = 1) {
		return apc_inc($this->prefix . $key, $step);
	}

	/**
	 * 原子递减指定键值。
	 * - $step 为整数，浮点数将被取整（非四舍五入）；
	 * - 成功，返回递减后的值；
	 * - 键值不存在或失败，返回 FALSE；
	 * 
	 * @param string $key
	 * @param int $step
	 * @return int|FALSE
	 */
	public function dec($key, $step = 1) {
		return apc_dec($this->prefix . $key, $step);
	}

	/**
	 * 删除指定键值。
	 * - 成功，返回 TRUE；
	 * - 键值不存在或失败，返回 FALSE。
	 * 
	 * @param string $key
	 * @return boolean
	 */
	public function del($key) {
		return apc_delete($this->prefix . $key);
	}

	/**
	 * 删除多个指定键值。
	 * - 成功，返回删除的数量，不存在的键值不计入其中，所以有可能为0。
	 * - 失败，返回0。
	 * 
	 * @param array $keys
	 * @return int
	 */
	public function mDel($keys) {
		$succ = 0;
		
		if (is_array($keys)) {
			foreach ($keys as $key) {
				apc_delete($this->prefix . $key) && $succ++;
			}
		} else {
			trigger_error(__CLASS__ . '::mDel() expects parameter 1 to be array', E_USER_WARNING);
		}
		
		return $succ;
	}

	/**
	 * 检测指定键值是否存在。
	 * - 存在，返回 TRUE；
	 * - 不存在，返回 FALSE；
	 * 
	 * @param string $key
	 * @return boolean
	 */
	public function exists($key) {
		if (is_scalar($key)) {
			return apc_exists($this->prefix . $key);
		}
		
		trigger_error(__CLASS__ . '::exists() expects parameter 1 to be scalar', E_USER_WARNING);
		
		return FALSE;
	}

	/**
	 * 获得当前前缀下的所有键。
	 * 
	 * @return array
	 */
	public function keys() {
		$keys = [];
		
		$l = strlen($this->prefix);
		
		$x = apc_cache_info('user')['cache_list'];
		foreach ($x as $item) {
			strpos($item['info'], $this->prefix) === 0 && $keys[] = substr($item['info'], $l);
		}
		
		return $keys;
	}

	/**
	 * 清空当前前缀下的所有键值。
	 * 
	 * @return void
	 */
	public function clear() {
		$x = apc_cache_info('user')['cache_list'];
		foreach ($x as $item) {
			strpos($item['info'], $this->prefix) === 0 && apc_delete($item['info']);
		}
	}

	public function __call($method, $args) {
		trigger_error("MCache drive 'APC' does not exist '" . $method . "' operation", E_USER_ERROR);
	}
}